package zcatt.examples.jface.snippets;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
import org.eclipse.jface.text.codemining.ICodeMining;
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
import org.eclipse.jface.text.codemining.LineContentCodeMining;
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.inlined.AbstractInlinedAnnotation;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Snippet012CodeMining {

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setLayout(new FillLayout());
		shell.setText("Snippet012CodeMining");
		
		SourceViewer sourceViewer = new SourceViewer(shell, null, SWT.BORDER|SWT.V_SCROLL);

		sourceViewer.setDocument(
				new Document("// Type class & new keyword and see references CodeMining\n"
						+ "// Name class with a number N to emulate Nms before resolving the references CodeMining \n\n"
						+ "class A\n" + "new A\n" + "new A\n\n" + "class 5\n" + "new 5\n" + "new 5\n" + "new 5"),
				new AnnotationModel());
		
		AnnotationPainter painter = addAnnPainter(sourceViewer);

		sourceViewer.setCodeMiningProviders(new ICodeMiningProvider[] {
			new ClassReferenceCodeMiningProvider(),
			new ClassImplementationCodeMiningProvider(),
			new ToEchoWithHeaderAndInlineCodeMiningProvider("class")			
		});
		
		painter.setAnnotationTypeColor(AbstractInlinedAnnotation.TYPE, new Color(255, 0, 0));

		MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() {

			@Override
			public void setDocument(IDocument document) {
				sourceViewer.updateCodeMinings();
			}

			@Override
			public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
				//ignore
			}

			@Override
			public void reconcile(IRegion partition) {
				sourceViewer.updateCodeMinings();
			}
		}, false);
		reconciler.install(sourceViewer);		
		
		shell.open();
		while(!shell.isDisposed())
		{
			if(!display.readAndDispatch())
				display.sleep();
		}
		shell.dispose();
	}

	
	
	public Snippet012CodeMining() {
	}
	
	static AnnotationPainter addAnnPainter(SourceViewer viewer)
	{
		IAnnotationAccess annAccess = new IAnnotationAccess() {
			@Override
			public Object getType(Annotation annotation) {
				return annotation.getType();
			}

			@Override
			public boolean isMultiLine(Annotation annotation) {
				return true;
			}

			@Override
			public boolean isTemporary(Annotation annotation) {
				return true;
			}

		};

		AnnotationPainter  painter = new AnnotationPainter(viewer, annAccess);
		viewer.addPainter(painter);
		viewer.setCodeMiningAnnotationPainter(painter);
		
		return painter;
	}

	static String getLineText(IDocument document, int line) {
		try {
			int lo = document.getLineOffset(line);
			int ll = document.getLineLength(line);
			return document.get(lo, ll);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	static class ClassReferenceCodeMiningProvider extends AbstractCodeMiningProvider
	{

		@Override
		public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
				IProgressMonitor monitor) {
			return CompletableFuture.supplyAsync(() -> {	//从textViewer中提取codeMining
				IDocument document = viewer.getDocument();
				List<ICodeMining> lenses = new ArrayList<>();
				int lineCount = document.getNumberOfLines();
				for (int i = 0; i < lineCount; i++) {
					
					// check if request was canceled.
					//monitor.isCanceled();
					
					String line = getLineText(document, i).trim();
					int index = line.indexOf("class ");
					if (index == 0) {
						String className = line.substring(index + "class ".length()).trim();
						if (!className.isEmpty()) {
							try {
								lenses.add(new ClassReferenceCodeMining(className, i, document, this));
							} catch (BadLocationException e) {
								e.printStackTrace();
							}
						}
					}
				}
				return lenses;
			});
		}
		
	}

	static class ClassImplementationCodeMiningProvider extends AbstractCodeMiningProvider
	{

		@Override
		public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
				IProgressMonitor monitor) {
			return CompletableFuture.supplyAsync(() -> {
				IDocument document = viewer.getDocument();
				List<ICodeMining> lenses = new ArrayList<>();
				int lineCount = document.getNumberOfLines();
				for (int i = 0; i < lineCount; i++) {
					// check if request was canceled.
					//monitor.isCanceled();
					updateContentMining(i, document, "class ", lenses);
					updateContentMining(i, document, "interface ", lenses);
				}
				return lenses;
			});
		}

		void updateContentMining(int lineIndex, IDocument document, String token, List<ICodeMining> lenses) {
			String line = getLineText(document, lineIndex).trim();
			int index = line.indexOf(token);
			if (index == 0) {
				String className = line.substring(index + token.length());
				index = className.indexOf(" ");		//zf, 
				if (index != -1) {
					className = className.substring(0, index);
				}
				if (!className.isEmpty()) {
					try {
						lenses.add(new ClassImplementationCodeMining(className, lineIndex, document, this));
					} catch (BadLocationException e) {
						e.printStackTrace();
					}
				}
			}
		}
		
	}
	
	static class ToEchoWithHeaderAndInlineCodeMiningProvider extends AbstractCodeMiningProvider
	{
		private final String toEcho;

		public ToEchoWithHeaderAndInlineCodeMiningProvider(String keyword) {
			this.toEcho = keyword;
		}

		@Override
		public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
				IProgressMonitor monitor) {
			int index = 0;
			List<ICodeMining> res = new ArrayList<>();
			while ((index = viewer.getDocument().get().indexOf(toEcho, index)) != -1) {
				index += toEcho.length();
				res.add(new LineContentCodeMining(new Position(index, 1), this) {
						@Override
						public String getLabel() {
							return toEcho+":";
						}
						
						@Override
						public boolean isResolved() {
							return true;
						}
					});
			};
			return CompletableFuture.completedFuture(res);
		}		
	}
	

	static class AbstractClassCodeMining extends LineHeaderCodeMining	
	{
		private final String className;

		public AbstractClassCodeMining(String className, int afterLineNumber, IDocument document,
				ICodeMiningProvider resolver) throws BadLocationException {
			super(afterLineNumber, document, resolver);
			this.className = className;
		}

		public String getClassName() {
			return className;
		}
	}
	
	static class ClassReferenceCodeMining extends AbstractClassCodeMining
	{
		private Object lock = new Object();

		public ClassReferenceCodeMining(String className, int afterLineNumber, IDocument document, ICodeMiningProvider provider)
				throws BadLocationException {
			super(className, afterLineNumber, document, provider);
		}

		@Override	//zf, override AbstractCodeMining.
		protected CompletableFuture<Void> doResolve(ITextViewer viewer, IProgressMonitor monitor) {
			return CompletableFuture.runAsync(() -> {
				IDocument document = viewer.getDocument();
				String className = super.getClassName();
				try {
					int wait = Integer.parseInt(className);
					try {
						for (int i = 0; i < wait; i++) {
							monitor.isCanceled();
							synchronized (lock) {
								lock.wait(1000);
							}
						}
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					}
				} catch (NumberFormatException e) {

				} catch (CancellationException e) {
					e.printStackTrace();
					throw e;
				}

				int refCount = 0;
				int lineCount = document.getNumberOfLines();
				for (int i = 0; i < lineCount; i++) {
					// check if request was canceled.
					monitor.isCanceled();
					String line = getLineText(document, i);
					refCount += line.contains("new " + className) ? 1 : 0;
				}
				super.setLabel(refCount + " references");
			});
		}
		
	}

	static class ClassImplementationCodeMining extends AbstractClassCodeMining
	{
		public ClassImplementationCodeMining(String className, int afterLineNumber, IDocument document,
				ICodeMiningProvider provider) throws BadLocationException {
			super(className, afterLineNumber, document, provider);
		}

		@Override
		protected CompletableFuture<Void> doResolve(ITextViewer viewer, IProgressMonitor monitor) {
			return CompletableFuture.runAsync(() -> {
				IDocument document = viewer.getDocument();
				String className = super.getClassName();
				int refCount = 0;
				int lineCount = document.getNumberOfLines();
				for (int i = 0; i < lineCount; i++) {
					// check if request was canceled.
					monitor.isCanceled();
					String line = getLineText(document, i);
					refCount += line.contains("implements " + className) ? 1 : 0;
				}
				super.setLabel(refCount + " implementation");
			});
		}		
	}
}
